import csv
import TrainByTrainIteration as tbti
import PyStationCapacityData as pscdata
import PyStationCapacity as psc
from datetime import datetime
import re


class Test:
    ''' 实验 '''
    Name = ""
    ''' 实验名称 '''

    CompStartTime = None
    ''' 求解开始时刻 '''
    
    CompEndTime = None
    ''' 求解结束时刻 '''
    
    CompTime = None
    ''' 求解耗费时间（总秒数） '''
    
    ObjVal = -1
    ''' 目标函数值 '''

    MIPGap = -1
    ''' 整数规划Gap '''

    OccupationFinishedTime = -1
    ''' 所有作业的完成时间 '''

    def calculateIndex(self):
        ''' 计算实验结果指标 '''

        self.CompTime = (self.CompEndTime - self.CompStartTime).total_seconds()  # 计算求解时间

        outputTimetableFileName = "./Output/"+test.Name+"/OutputTimetable.csv"  # 求解结果运行图文件
        cellOccupancyFileName = "./Output/"+test.Name+"/CellOccupancy.csv"  # 求解结果轨道电路占用文件

        # 列车种类及其数量
        totalTrainNum = 0
        trainTypeNumDict = dict()
        trainDict = dict()

        f = open(outputTimetableFileName, "r")
        for item in csv.DictReader(f):
            if item["TrainID"] in trainDict:
                train = trainDict[item["TrainID"]]
            else:
                train = pscdata.Train()
                train.ID = item["TrainID"]
                trainDict[item["TrainID"]] = train
                totalTrainNum += 1
                trainTypeTag = re.search("[A-Z]+", train.ID).group()  # 利用正则表达式对列车进行分类，按列车的开头字母组合
                if trainTypeTag in trainTypeNumDict:
                    trainTypeNumDict[trainTypeTag] += 1
                else:
                    trainTypeNumDict[trainTypeTag] = 1
        
        self.TotalTrainNum = totalTrainNum  # 列车总数量统计
        self.TrainTypeNum = trainTypeNumDict  # 按分类的列车数量统计

        # 作业完成时间（最后一个cell occupation结束的时刻）
        f = open(cellOccupancyFileName, "r")
        maxEndTime = 0
        for item in csv.DictReader(f):
            endTimeItem = item["OccupancyEndTime"].split(':')
            endTime = (
                int(endTimeItem[0])*3600+int(endTimeItem[1])*60+int(endTimeItem[2]))/60
            if endTime > maxEndTime:
                maxEndTime = endTime
        self.OccupationFinishedTime = maxEndTime

        # 通过能力(根据UIC406换算得到)
        self.TotalOccupationTime = self.OccupationFinishedTime  # 总占用时间（分钟）
        self.OccupationRate = self.TotalOccupationTime / 1080  # 按一天18小时（运行时段6:00—24:00）计算的利用率
        self.CapacityVal = totalTrainNum/self.OccupationRate  # 以利用率反算的车站通过能力


TestList = list()
''' 实验列表 '''


# 加载实验配置
f = open("./LocalData/TestBed/TestBedConf.csv", "r")
for item in csv.DictReader(f):
    test = Test()
    test.Name = item["TestName"]
    test.Strategy = item["Strategy"]
    test.ObjFunc = item["ObjFunc"]
    test.TrainFileName = item["TrainFileName"]
    test.TimetableFileName = item["TimetableFileName"]
    test.RouteFileName = item["RouteFileName"]
    test.StationRouteTimeFileName = item["StationRouteTimeFileName"]
    test.LeftShiftTolerance = int(item["LeftShiftTolerance"])
    test.RightShiftTolerance = int(item["RightShiftTolerance"])
    TestList.append(test)

# 初始化
f=open("./Output/ResultIndex.csv","w")
f.writelines("Name,CompStartTime,CompEndTime,CompTime,TotalTrainNum,OccupationFinishedTime,TotalOccupationTime,OccupationRate,ObjVal,MIPGap,CapacityVal,TrainTypeNote\n")
f.close()

# 循环进行实验
for test in TestList:
    print("Begin test \"{0}\"!".format(test.Name))
    test.CompStartTime = datetime.now()  # 记录求解开始时刻

    if test.Strategy == "Gurobi" or test.Strategy == "CP-SAT":  # 使用Gurobi或CP-SAT求解器直接求解
        psc.Data = pscdata.PSCData()  # 创建数据对象
        psc.Data.InputFileDirectory = "./LocalData/TestBed"  # 输入数据目录
        psc.Data.TrainFileName = test.TrainFileName  # 列车数据文件名
        psc.Data.TimetableFileName = test.TimetableFileName  # 原始时刻表数据文件名
        psc.Data.RouteFileName = test.RouteFileName  # 原始进路数据文件名
        psc.Data.StationRouteTimeFileName = test.StationRouteTimeFileName  # 原始进路数据占用时间文件名
        psc.Data.LeftShiftTolerance = test.LeftShiftTolerance  # 作业时间左移容忍量
        psc.Data.RightShiftTolerance = test.RightShiftTolerance  # 作业时间右移容忍量
        psc.Data.OutputFileDirectory = "./Output/"+test.Name  # 输出文件目录

        psc.Data.LoadData()  # 从文件中读取数据

        psc.ObjFunc = test.ObjFunc  # 设置目标函数
        psc.AppliedSolverName = test.Strategy  # 设置求解器

        psc.Initialize()  # 初始化
        psc.BuildModel()  # 创建模型
        solutionStatus = psc.Solve()  # 求解模型

        if solutionStatus == True:  # 如果模型求解成功，则输出运行图数据
            psc.Data.WriteSolution()  # 输出求解结果
            psc.Data.WriteMetaData(
                psc.Data.OutputFileDirectory+"/MetaData.xml")
            test.ObjVal = psc.ObjVal  # 目标函数
            test.MIPGap = psc.MIPGap  # 混合整数规划Gap

    elif test.Strategy == "GurobiFixing":  # 使用Gurobi求解器进行固定顺序循环加车求解
        tbti.Data = pscdata.PSCData()  # 创建数据对象
        tbti.Data.InputFileDirectory = "./LocalData/TestBed"
        tbti.Data.TrainFileName = test.TrainFileName
        tbti.Data.TimetableFileName = test.TimetableFileName
        tbti.Data.RouteFileName = test.RouteFileName
        tbti.Data.StationRouteTimeFileName = test.StationRouteTimeFileName
        tbti.Data.LeftShiftTolerance = test.LeftShiftTolerance
        tbti.Data.RightShiftTolerance = test.RightShiftTolerance
        tbti.Data.OutputFileDirectory = "./Output/"+test.Name

        tbti.Data.LoadData()  # 从文件中读取数据

        tbti.ObjFunc = test.ObjFunc  # 设置目标函数
        tbti.AppliedSolverName = "Gurobi"  # 指定使用Gurobi作为固定顺序循环加车求解的引擎

        tbti.DoTrainByTrainIteration()  # 执行循环加车求解

        test.ObjVal = tbti.ObjVal  # 目标函数

    test.CompEndTime = datetime.now()  # 记录求解结束时刻
    print("Test \"{0}\" finished!".format(test.Name))

    # 保存当前测试数据
    if test.ObjVal != -1:
        test.calculateIndex()  # 根据求解结果计算各项指标
        
    with open("./Output/ResultIndex.csv", "a") as f:
        f.writelines("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},\"{11}\"\n".format(
            test.Name,
            test.CompStartTime, test.CompEndTime, test.CompTime, test.TotalTrainNum, test.OccupationFinishedTime,
            test.TotalOccupationTime, test.OccupationRate, test.ObjVal, test.MIPGap, test.CapacityVal, test.TrainTypeNum))
        f.close()
